package com.prp.keywords_search_statistic;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.prp.db.po.KeywordsLib;

/**
 * 由HashMap保存从“关键词”到“实体ID”的映射 LinkedListNode封装了关键字从加载到内存开始的读取次数
 * 由LinkedList根据读取次数对关键字进行排序 CacheInteger封装了实体ID所占用的字节数，和对LinkedListNode的引用
 * 
 * 使用读/写锁，对对象的读写进行加锁
 */
public class Cache {

	private static final long serialVersionUID = -5564761224852265395L;

	private HashMap<String, CacheInteger> map;// 保存从“关键词”到“实体ID”的映射

	private long maxCacheSize;// 最大容许的存储的关键词条数
	private long size = 0L;

	private long hints = 0L;// 搜索命中次数
	private long misses = 0L;// 搜索未命中次数

	private LinkedList LRUList;// 按照关键字读取次数对关键字进行排序的链表

	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private Lock readLock = rwl.readLock();
	private Lock writeLock = rwl.writeLock();

	public Cache(long maxSize) {
		map = new HashMap<String, CacheInteger>();
		this.maxCacheSize = maxSize;
		LRUList = new LinkedList();
		loadData();
	}

	/**
	 * 第一次加载或者重新加载数据库数据时使用
	 */
	public void loadData() {
			// 从数据库随意加载一些关键字出来
		List<KeywordsLib> keywords = DatabaseProxy
					.loadData((int) (maxCacheSize * Constrains.PREFER_PERCENT));
		
		this.clear();
		//开始向cache中添加内容
		writeLock.lock();
		try {
			String keyword;
			Integer id;
			KeywordsLib keywordsObject;
			Iterator<KeywordsLib> iter = keywords.iterator();
			while (iter.hasNext()) {
				keywordsObject = iter.next();
				keyword = keywordsObject.getKeyWord();
				id = keywordsObject.getChildCategoryId();

				if (map.containsKey(keyword)) {// 如果该关键字已经被加入过了
					
					boolean flag = map.get(keyword).add(id);
					if( flag ){
						size++;
					}
				} else {
					CacheInteger cacheId = new CacheInteger(id);
					LinkedListNode node = LRUList.addLast(keyword);
					cacheId.lruNode = node;
					map.put(keyword, cacheId);
					size++;
				}
				
			}
		} finally {
			writeLock.unlock();
		}
	}

	public HashMap<String, CacheInteger> getMap() {
		return map;
	}

	public static void main(String[] args) {
		Cache cache = new Cache(500);
		HashMap<String, CacheInteger> map = cache.getMap();
		System.out.println("map中不重复的元素"+map.size());
		Set<String> set = map.keySet();
		System.out.println("map中的所有元素"+set.toString());
		int result = 0;
		for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
			result += map.get(iter.next()).size();
		}
		System.out.println("一共"+result+"个键值对；");
		System.out.println("下面打印的是搜寻几次之后cache中存储元素的变化情况");
		cache.search("44");
		cache.search("33");
		cache.search("33");
		cache.search("44");
		cache.search("44");
		cache.search("44");
		cache.search("90");
		cache.search("60");
		cache.search("62");
		cache.search("61");
		cache.search("69");
		cache.search("69");
		cache.search("69");
		cache.search("69");
		cache.search("69");
		cache.search("69");
//		System.out.println(set2.toString());
	}

	public long getMaxCacheSize() {
		return this.maxCacheSize;
	}

	public void setMaxCacheSize(long size) {
		maxCacheSize = size;
		cullCache();
	}

	/**
	 * 返回一共有的键值数
	 */
	public long getCacheSize() {
		return size;
	}

	/**
	 * 不重复的关键字的数目
	 * @return
	 */
	public int getMapSize(){
		return map.size();
	}
	public long getCacheHints() {
		return hints;
	}

	public long getCacheMisses() {
		return misses;
	}

	/**
	 * 
	 * 清空缓存
	 */
	public void clear() {
		writeLock.lock();
		try {
			map.clear();
			hints = 0L;
			misses = 0L;
			LRUList.clear();
			size = 0;
		} finally {
			writeLock.unlock();
		}

	}

	/**
	 * 缓存是否为空
	 */
	public boolean isEmpty() {
		return map.isEmpty();
	}

	/**
	 * 将数据放入缓存,如果该关键字已经存在，则将entityID加入value中， 若不存在则将其加入
	 */
	public void put(String keyword, Integer entityID) {
		// 使用前先检查cache是否需要清理
		cullCache();
		
		writeLock.lock();
		HashSet<Integer> result = this.get(keyword);
		try {
			if (result == null) {
				CacheInteger cacheId = new CacheInteger(entityID);
				LinkedListNode node = LRUList.addLast(keyword);
				// 关键字刚刚放到内存中时hint = 0
				cacheId.lruNode = node;
				map.put(keyword, cacheId);
				size++;
			} else {
				boolean flag = result.add(entityID);
				if( flag){
					size++;
				}
			}
		} finally {
			writeLock.unlock();
		}
	}

//	public void put(String keyword, HashSet<Integer> ids) {
//		// 使用前先检查cache是否需要清理
//		cullCache();
//
//		HashSet<Integer> result = this.get(keyword);
//		writeLock.lock();
//		try {
//			if (result == null) {
//				CacheInteger cacheId = new CacheInteger(ids);
//				LinkedListNode node = LRUList.addLast(keyword);
//				// 关键字刚刚放到内存中时hint = 0
//				cacheId.lruNode = node;
//				map.put(keyword, cacheId);
//			} else {
//				result.addAll(ids);
//			}
//		} finally {
//			writeLock.unlock();
//		}
//	}

	/**
	 * 当缓存的容量只剩余3%时，根据LRU原则删掉最少使用的关键字， 使得缓存的容量空余量达到10%以上
	 */
	private final void cullCache() {
		if (maxCacheSize <= 0) {
			return;
		}
		if (getCacheSize() >= maxCacheSize * Constrains.MAX_PERCENT) {
			long desiredSize = (long) (maxCacheSize * Constrains.PREFER_PERCENT);
			while (getCacheSize() > desiredSize) {
				remove(LRUList.getLast().getKey());
			}
		}
	}

	/**
	 * 在cache中删掉keyword
	 * 
	 * @param keyword
	 * @return
	 */
	public HashSet<Integer> remove(String keyword) {
		CacheInteger cacheInteger = map.get(keyword);
		if (cacheInteger == null) {
			return null;
		}

		writeLock.lock();
		try {
			CacheInteger removed = map.remove(keyword);
			if(removed != null){
				size -= removed.size();
				LRUList.remove(cacheInteger.lruNode);
				cacheInteger.lruNode = null;
			}
		} finally {
			writeLock.unlock();
		}
		return cacheInteger.getIds();
	}

	/**
	 * 不可以加锁，因为在写入的时候会调用
	 * @param keyword
	 * @return
	 */
	private HashSet<Integer> get(String keyword) {
		CacheInteger cahcheInteger = map.get(keyword);
		if (cahcheInteger == null) {
			return null;
		} else {
			return cahcheInteger.getIds();
		}
	}

	/**
	 * 如果缓存中有关键字，则将子类别返回。 若没有，查找数据库中是否有此关键字。
	 * 若关键字在数据库中，则将关键字加入到cache中，并且将entityID返回。如果buf已满，则采用相应的替换策略。
	 * 若关键字没有在数据库中，则将关键字暂时保存到EtcKey表格里面，供管理员分类添加，并返回null。
	 */
	public HashSet<Integer> search(String keyword) {
		CacheInteger cacheInteger;
		readLock.lock();
		try{
			cacheInteger = map.get(keyword);
		}finally{
			readLock.unlock();
		}
		if (cacheInteger == null) {
			// 找不到特殊处理
			List<KeywordsLib> keywordObjects = DatabaseProxy
					.findKeyword(keyword);
			if (keywordObjects == null || keywordObjects.size() == 0) {
				// 说明该数据没有在数据库中,放到临时表中，返回null
				return null;
			} else {
				// 说明该数据在数据库中，加入到cache中
				// 并且返回用户需要的数据
				HashSet<Integer> temp = new HashSet<Integer>();
				Iterator<KeywordsLib> iter = keywordObjects.iterator();
				for ( ; iter.hasNext(); ) {
					KeywordsLib k = iter.next();
					this.put(keyword, k.getChildCategoryId());
					temp.add(k.getChildCategoryId());
				}
//				this.put(keyword, temp);
				this.misses++;
				return temp;
			}
		}

		this.hints++;
		cacheInteger.lruNode.increaseCount(1);
		LRUList.shiftUp(cacheInteger.lruNode);
		System.out.println(LRUList);
		
		return cacheInteger.getIds();
	}

	/**
	 * 判断关键字是否在缓存中
	 */
	public boolean constainsKey(String key) {
		return map.containsKey(key);
	}

	private static class CacheInteger {

		public HashSet<Integer> Ids;
		public LinkedListNode lruNode;// 关联的链表结点

		public CacheInteger(Integer id) {
			Ids = new HashSet<Integer>();
			Ids.add(id);
		}

		public CacheInteger(HashSet<Integer> IDS) {
			this.Ids = IDS;
		}

		public boolean add(Integer id) {
			return Ids.add(id);
		}

		public HashSet<Integer> getIds() {
			return Ids;
		}

		public String toString() {
			return Ids.toString();
		}

		public int size() {
			return Ids.size();
		}
	}

}
